package ru.amse.tsyganov.jumleditor.storing;

import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import ru.amse.tsyganov.jumleditor.model.ActionState;
import ru.amse.tsyganov.jumleditor.model.ActivityGraph;
import ru.amse.tsyganov.jumleditor.model.Decision;
import ru.amse.tsyganov.jumleditor.model.FinalState;
import ru.amse.tsyganov.jumleditor.model.ForkJoinState;
import ru.amse.tsyganov.jumleditor.model.InitialState;
import ru.amse.tsyganov.jumleditor.model.State;
import ru.amse.tsyganov.jumleditor.model.Transition;
import ru.amse.tsyganov.jumleditor.storing.statefactories.AbstractStateFactory;
import ru.amse.tsyganov.jumleditor.storing.statefactories.ActionStateFactory;
import ru.amse.tsyganov.jumleditor.storing.statefactories.DecisionFactory;
import ru.amse.tsyganov.jumleditor.storing.statefactories.FinalStateFactory;
import ru.amse.tsyganov.jumleditor.storing.statefactories.ForkJoinStateFactory;
import ru.amse.tsyganov.jumleditor.storing.statefactories.InitialStateFactory;
import ru.amse.tsyganov.jumleditor.view.ConnectionPoint;
import ru.amse.tsyganov.jumleditor.view.repr.ActivityGraphView;
import ru.amse.tsyganov.jumleditor.view.repr.StateView;
import ru.amse.tsyganov.jumleditor.view.repr.TransitionView;

public class XmlReader {
	
	private static final Map<String, AbstractStateFactory> FACTORIES =
		new HashMap<String, AbstractStateFactory>();
	
	static {
		FACTORIES.put(ActionState.ACTION_STATE, new ActionStateFactory());
		FACTORIES.put(FinalState.FINAL_STATE, new FinalStateFactory());
		FACTORIES.put(Decision.DECISION, new DecisionFactory());
		FACTORIES.put(ForkJoinState.FORK_JOIN_STATE, new ForkJoinStateFactory());
		FACTORIES.put(InitialState.INITIAL_STATE, new InitialStateFactory());
	}
	
	private final Map<Integer, StateView<? extends State>> idToStateViewMap = 
		new HashMap<Integer, StateView<? extends State>>();
	
	private final Map<Integer, Transition> idToTransitionMap = 
		new HashMap<Integer, Transition>();
	
	private ActivityGraph activityGraph;
	
	private ActivityGraphView<ActivityGraph> activityGraphView;

	public XmlReader() {
		
	}
	
	public ActivityGraphView<ActivityGraph> readFromDocument(Document document) 
		throws StoreException {
		
		activityGraph = new ActivityGraph();
		activityGraphView = new ActivityGraphView<ActivityGraph>(activityGraph);
		
		readActivityGraphModel(document);
		readStates(document);
		readTransitions(document);
				
		readActivityGraphView(document);
		readStateViews(document);
		readTransitionViews(document);
		
		return activityGraphView;
	}
	
	private void readActivityGraphModel(Document document) {
		assert activityGraph != null;
		
		Node activityGraphNode = document.
			getElementsByTagName(TagNames.ACTIVITY_GRAPH_MODEL).
				item(0);
		
		activityGraph.readFromXml(activityGraphNode.getFirstChild());
	}
	
	private void readStates(Document document) {
		assert activityGraphView != null;
		
		NodeList stateNodeList = document.
			getElementsByTagName(TagNames.STATE_MODEL);
		
		for (int i = 0; i < stateNodeList.getLength(); i++) {
			Node stateNode = stateNodeList.item(i);
			String className = stateNode.getFirstChild().
				getAttributes().getNamedItem(TagNames.CLASS_NAME).
					getNodeValue();
			
			AbstractStateFactory factory = FACTORIES.get(className);
			StateView<? extends State> stateView = 
				factory.createStateAndView(activityGraphView);
			
			stateView.getModel().readFromXml(stateNode.getFirstChild());
			
			int id = Integer.parseInt(stateNode.getAttributes().
					getNamedItem(TagNames.ID).getNodeValue());
			
			idToStateViewMap.put(id, stateView);
		}
	}
	
	private void readTransitions(Document document) throws StoreException {
		NodeList transitionNodeList = document.
			getElementsByTagName(TagNames.TRANSITION_MODEL);
		
		for (int i = 0; i < transitionNodeList.getLength(); i++) {
			
			Node transitionNode = transitionNodeList.item(i);
			
			int sourceStateId = Integer.parseInt(
					transitionNode.getAttributes().
					getNamedItem(TagNames.TRANSITION_SOURCE_ID).
						getNodeValue());
			
			int targetStateId = Integer.parseInt(
					transitionNode.getAttributes().
					getNamedItem(TagNames.TRANSITION_TARGET_ID).
						getNodeValue());
			
			if (!idToStateViewMap.containsKey(sourceStateId) || 
					!idToStateViewMap.containsKey(targetStateId)) {
				
				throw new StoreException("Error in xml references");
			}
			
			State source = idToStateViewMap.get(sourceStateId).getModel();
			State target = idToStateViewMap.get(targetStateId).getModel();
			
			Transition t = new Transition(
					source.getStateVertex(),
					target.getStateVertex());
			
			t.addBindings();
			
			t.readFromXml(transitionNode.getFirstChild());
			
			int id = Integer.parseInt(transitionNode.getAttributes().
					getNamedItem(TagNames.ID).getNodeValue());
			
			idToTransitionMap.put(id, t);
		}
	}
	
	private void readActivityGraphView(Document document) {
		assert activityGraphView != null;
	}
	
	private void readStateViews(Document document) throws StoreException {
		assert activityGraphView != null;
		
		NodeList stateViewNodeList = document.
			getElementsByTagName(TagNames.STATE_VIEW);
		
		for (int i = 0; i < stateViewNodeList.getLength(); i++) {
			Node stateViewNode = stateViewNodeList.item(i);
			
			int id = Integer.parseInt(stateViewNode.getAttributes().
					getNamedItem(TagNames.ID).getNodeValue());
			
			if (!idToStateViewMap.containsKey(id)) {
				throw new StoreException("Error in xml references");
			}
			StateView<? extends State> stateView = idToStateViewMap.get(id);

			Node element = stateViewNode.getFirstChild();
			stateView.setX(Integer.parseInt(element.getAttributes().
					getNamedItem(TagNames.STATE_VIEW_X).getNodeValue()));
			stateView.setY(Integer.parseInt(element.getAttributes().
					getNamedItem(TagNames.STATE_VIEW_Y).getNodeValue()));
			stateView.setWidth(Integer.parseInt(element.getAttributes().
					getNamedItem(TagNames.STATE_VIEW_WIDTH).getNodeValue()));
			stateView.setHeight(Integer.parseInt(element.getAttributes().
					getNamedItem(TagNames.STATE_VIEW_HEIGHT).getNodeValue()));
		}
	}

	private void readTransitionViews(Document document) throws StoreException {
		NodeList transitionViewNodeList = document.
			getElementsByTagName(TagNames.TRANSITION_VIEW);
		
		for (int i = 0; i < transitionViewNodeList.getLength(); i++) {
			Node transitionViewNode = transitionViewNodeList.item(i);
			
			int id = Integer.parseInt(transitionViewNode.getAttributes().
					getNamedItem(TagNames.ID).getNodeValue());
					
			if (!idToTransitionMap.containsKey(id)) {
				throw new StoreException("Error in xml references");
			}
			Transition transition = idToTransitionMap.get(id);
			
			int sourceConnPointIndex = Integer.valueOf(
					transitionViewNode.getAttributes().
					getNamedItem(TagNames.TRANSITION_SOURCE_CONN_POINT).getNodeValue());
			
			int targetConnPointIndex = Integer.valueOf(
					transitionViewNode.getAttributes().
					getNamedItem(TagNames.TRANSITION_TARGET_CONN_POINT).getNodeValue());
			
			ConnectionPoint sourceConnPoint = 
					activityGraphView.getModelViewMap().getStatesMap().get(
						transition.getSource().getContainer()).getConnectionPoints().
							get(sourceConnPointIndex);
			
			ConnectionPoint targetConnPoint = 
					activityGraphView.getModelViewMap().getStatesMap().get(
						transition.getTarget().getContainer()).getConnectionPoints().
							get(targetConnPointIndex);
			
			TransitionView<Transition> transitionView =  
				new TransitionView<Transition>(transition, activityGraphView,
						sourceConnPoint, targetConnPoint);
			
			int v = Integer.valueOf(
					transitionViewNode.getAttributes().
					getNamedItem(TagNames.VERTICAL_SEGMENT).getNodeValue());
			
			int h = Integer.valueOf(
					transitionViewNode.getAttributes().
					getNamedItem(TagNames.HORIZONTAL_SEGMENT).getNodeValue());
			
			transitionView.initBrokenLine(v, h);
			
			activityGraphView.getModelViewMap().getTransitionsMap().
				put(transition, transitionView);
		}
	}
}
